
#include "StdAfx.h"
#include "ArchiveCallback.h"
#include <stdio.h>

static const wchar_t *kEmptyFileAlias = L"[Content]";

static FString Format_String( const wchar_t* xxx, ... )
{
	wchar_t ch[2048] = {0};
	va_list arg_ptr;
	va_start(arg_ptr, xxx);
	swprintf(ch,2048,xxx,arg_ptr);
	va_end( arg_ptr );

	return FString(ch);
}
static FString GetFreeFileName(const FString& chName)
{
	int iNum = 0;
	FString strName = Format_String(L"%s_%dold",chName.Ptr(),iNum++);
	NFind::CFileInfo fi;
	while ( !fi.Find(chName) )
	{
		strName = Format_String(L"%s_%dold",chName.Ptr(),iNum++);
	}
	return strName;
}

static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result)
{
	NCOM::CPropVariant prop;
	RINOK(archive->GetProperty(index, propID, &prop));
	if (prop.vt == VT_BOOL)
		result = VARIANT_BOOLToBool(prop.boolVal);
	else if (prop.vt == VT_EMPTY)
		result = false;
	else
		return E_FAIL;
	return S_OK;
}
static HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result)
{
	return IsArchiveItemProp(archive, index, kpidIsDir, result);
}
STDMETHODIMP CArchiveOpenCallback::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */)
{
	return S_OK;
}
STDMETHODIMP CArchiveOpenCallback::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */)
{
	return S_OK;
}
STDMETHODIMP CArchiveOpenCallback::CryptoGetTextPassword(BSTR *password)
{
	if (!m_PasswordIsDefined)
	{
		// You can ask real password here from user
		// Password = GetPassword(OutStream);
		// PasswordIsDefined = true;
		return E_ABORT;
	}
	return StringToBstr(m_Password, password);
}
void CArchiveExtractCallback::Init(IInArchive *archiveHandler, const FString &directoryPath)
{
	m_NumErrors = 0;
 	_archiveHandler = archiveHandler;
 	_directoryPath = directoryPath;
	NName::NormalizeDirPathPrefix(_directoryPath);
}
STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 /* size */)
{
	return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 * /* completeValue */)
{
	return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index,ISequentialOutStream **outStream, Int32 askExtractMode)
{
 	*outStream = 0;
	_outFileStream.Release();
 	{
 		// Get Name
 		NCOM::CPropVariant prop;
 		RINOK(_archiveHandler->GetProperty(index, kpidPath, &prop));
 
 		UString fullPath;
 		if (prop.vt == VT_EMPTY)
			fullPath = kEmptyFileAlias;
 		else
 		{
 			if (prop.vt != VT_BSTR)
 				return E_FAIL;
 			fullPath = prop.bstrVal;
 		}
 		_filePath = fullPath;
 	}
	
 
 	if (askExtractMode != NArchive::NExtract::NAskMode::kExtract)
 		return S_OK;
 
 	{
 		// Get Attrib
 		NCOM::CPropVariant prop;
 		RINOK(_archiveHandler->GetProperty(index, kpidAttrib, &prop));
 		if (prop.vt == VT_EMPTY)
 		{
 			_processedFileInfo.Attrib = 0;
 			_processedFileInfo.AttribDefined = false;
 		}
 		else
 		{
 			if (prop.vt != VT_UI4)
 				return E_FAIL;
 			_processedFileInfo.Attrib = prop.ulVal;
 			_processedFileInfo.AttribDefined = true;
 		}
 	}
 
 	RINOK(IsArchiveItemFolder(_archiveHandler, index, _processedFileInfo.isDir));
 
 	{
 		// Get Modified Time
 		NCOM::CPropVariant prop;
 		RINOK(_archiveHandler->GetProperty(index, kpidMTime, &prop));
 		_processedFileInfo.MTimeDefined = false;
 		switch (prop.vt)
 		{
 		case VT_EMPTY:
 			// _processedFileInfo.MTime = _utcMTimeDefault;
 			break;
 		case VT_FILETIME:
 			_processedFileInfo.MTime = prop.filetime;
 			_processedFileInfo.MTimeDefined = true;
 			break;
 		default:
 			return E_FAIL;
 		}
 
 	}
 	{
 		// Get Size
 		NCOM::CPropVariant prop;
 		RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop));
 		UInt64 newFileSize;
 		/* bool newFileSizeDefined = */ ConvertPropVariantToUInt64(prop, newFileSize);
 	}
	
	FString fullProcessedPath;

	if ( m_pfn )
	{
		TCHAR ch[260]= {0};
		m_pfn(index,ch,260,m_param);
		fullProcessedPath = ch;
	}
	else
	{
		fullProcessedPath = _directoryPath + us2fs(_filePath);
	}
	// Create folders for file
	int slashPos = fullProcessedPath.ReverseFind_PathSepar();
	if (slashPos >= 0)
		CreateComplexDir(fullProcessedPath.Left(slashPos));


	_diskFilePath = fullProcessedPath;
 	if (_processedFileInfo.isDir)
 	{
 		CreateComplexDir(fullProcessedPath);
 	}
 	else
 	{
 		NFind::CFileInfo fi;
 		if (fi.Find(fullProcessedPath))
 		{
 			if (!DeleteFileAlways(fullProcessedPath))
 			{
				if ( true )
				{
					FString str = GetFreeFileName(fullProcessedPath);
					MoveFileEx(fullProcessedPath.Ptr(),str.Ptr(),MOVEFILE_REPLACE_EXISTING );
					MoveFileEx(str.Ptr(),NULL,MOVEFILE_DELAY_UNTIL_REBOOT);
				}
				else
				{
					return E_ABORT;
				}
 			}
 		}
 
  		_outFileStreamSpec = new COutFileStream;
  		CMyComPtr<ISequentialOutStream> outStreamLoc(_outFileStreamSpec);
  		if (!_outFileStreamSpec->Open(fullProcessedPath, CREATE_ALWAYS))
  		{
  			return E_ABORT;
  		}
  		_outFileStream = outStreamLoc;
  		*outStream = outStreamLoc.Detach();
 	}
 	return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)
{
 	_extractMode = false;
 	switch (askExtractMode)
 	{
 	case NArchive::NExtract::NAskMode::kExtract: 
		_extractMode = true; 
		break;
 	};
 	switch (askExtractMode)
 	{
 	case NArchive::NExtract::NAskMode::kExtract:
		break;
 	case NArchive::NExtract::NAskMode::kTest:
		break;
 	case NArchive::NExtract::NAskMode::kSkip:
		break;
 	};
 	return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult)
{
 	switch (operationResult)
 	{
 	case NArchive::NExtract::NOperationResult::kOK:
 		break;
 	default:
 		{
 			m_NumErrors++;
 			switch (operationResult)
 			{
 			case NArchive::NExtract::NOperationResult::kUnsupportedMethod:
 				break;
 			case NArchive::NExtract::NOperationResult::kCRCError:		
 				break;
 			case NArchive::NExtract::NOperationResult::kDataError:
 				break;
 			case NArchive::NExtract::NOperationResult::kUnavailable:
 				break;
 			case NArchive::NExtract::NOperationResult::kUnexpectedEnd:
 				break;
 			case NArchive::NExtract::NOperationResult::kDataAfterEnd:
 				break;
 			case NArchive::NExtract::NOperationResult::kIsNotArc:
 				break;
 			case NArchive::NExtract::NOperationResult::kHeadersError:
 				break;
 			}
 		}
 	}
 
 	if (_outFileStream)
 	{
 		if (_processedFileInfo.MTimeDefined)
 			_outFileStreamSpec->SetMTime(&_processedFileInfo.MTime);
 		RINOK(_outFileStreamSpec->Close());
 	}
 	_outFileStream.Release();
 	if (_extractMode && _processedFileInfo.AttribDefined)
 		SetFileAttrib(_diskFilePath, _processedFileInfo.Attrib);
 	return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)
{
 	if (!m_PasswordIsDefined)
 	{
 		// You can ask real password here from user
 		// Password = GetPassword(OutStream);
 		// PasswordIsDefined = true;
 		return E_ABORT;
 	}
 	return StringToBstr(m_Password, password);
}


STDMETHODIMP CArchiveUpdateCallback::SetTotal(UInt64 /* size */)
{
	return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::SetCompleted(const UInt64 * /* completeValue */)
{
	return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 index, Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive)
{
	const CDirItem &dirItem = (*m_DirItems)[index];
	if (newData)
		*newData = dirItem.isNewdata;
	if (newProperties)
		*newProperties = dirItem.isNewproperties;
	if (indexInArchive)
		*indexInArchive = dirItem.indexInArchive;
	return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{
	NCOM::CPropVariant prop;

	if (propID == kpidIsAnti)
	{
		prop = false;
		prop.Detach(value);
		return S_OK;
	}

	{
		const CDirItem &dirItem = (*m_DirItems)[index];
		switch (propID)
		{
		case kpidPath:  
			prop = dirItem.Name; 
			break;
		case kpidIsDir:  
			dirItem.isNewdata ? prop = dirItem.isDir():false; 
			break;
		case kpidSize:  
			dirItem.isNewdata ? prop = dirItem.Size:false; 
			break;
		case kpidAttrib: 
			dirItem.isNewdata ? prop = dirItem.Attrib:false; 
			break;
		case kpidCTime:  
			dirItem.isNewdata ? prop = dirItem.CTime:false; 
			break;
		case kpidATime:  
			dirItem.isNewdata ? prop = dirItem.ATime:false; 
			break;
		case kpidMTime:  
			dirItem.isNewdata ? prop = dirItem.MTime:false; 
			break;
		}
	}
	prop.Detach(value);
	return S_OK;
}

HRESULT CArchiveUpdateCallback::Finilize()
{
	if (m_NeedBeClosed)
	{
		m_NeedBeClosed = false;
	}
	return S_OK;
}
static void GetStream2(const wchar_t *name)
{
	if (name[0] == 0)
		name = kEmptyFileAlias;
}
STDMETHODIMP CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream **inStream)
{
	RINOK(Finilize());

	const CDirItem &dirItem = (*m_DirItems)[index];
	GetStream2(dirItem.Name);

	if (dirItem.isDir())
		return S_OK;

	{
		CInFileStream *inStreamSpec = new CInFileStream;
		CMyComPtr<ISequentialInStream> inStreamLoc(inStreamSpec);
		FString path = m_DirPrefix + dirItem.FullPath;
		if (!inStreamSpec->Open(path))
		{
			DWORD sysError = ::GetLastError();
			m_FailedCodes.Add(sysError);
			m_FailedFiles.Add(path);

			return S_FALSE;
		}
		*inStream = inStreamLoc.Detach();
	}
	return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::SetOperationResult(Int32 /* operationResult */)
{
	m_NeedBeClosed = true;
	return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size)
{
	if (m_VolumesSizes.Size() == 0)
		return S_FALSE;
	if (index >= (UInt32)m_VolumesSizes.Size())
		index = m_VolumesSizes.Size() - 1;
	*size = m_VolumesSizes[index];
	return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream)
{
	wchar_t temp[16];
	ConvertUInt32ToString(index + 1, temp);
	UString res = temp;
	while (res.Len() < 2)
		res.InsertAtFront(L'0');
	UString fileName = m_VolName;
	fileName += L'.';
	fileName += res;
	fileName += m_VolExt;
	COutFileStream *streamSpec = new COutFileStream;
	CMyComPtr<ISequentialOutStream> streamLoc(streamSpec);
	if (!streamSpec->Create(us2fs(fileName), false))
		return ::GetLastError();
	*volumeStream = streamLoc.Detach();
	return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password)
{
	if (!m_PasswordIsDefined)
	{
		if (m_AskPassword)
		{
			// You can ask real password here from user
			// Password = GetPassword(OutStream);
			// PasswordIsDefined = true;
			return E_ABORT;
		}
	}
	*passwordIsDefined = BoolToInt(m_PasswordIsDefined);
	return StringToBstr(m_Password, password);
}